#include <cstdlib>
#include <iostream>
#include <string>
#include <unistd.h>

#include "BotonPartida.h"
#include "Juego.h"
#include "VistaButton.h"
#include "VistaMiniBar.h"
#include "VistaGoldenStar.h"
#include "Mensaje.h"
#include "Mutex.h"
#include "Usuario.h"
#include "Protocolo.h"
#include "SalaPartida.h"
#include <map>
#include "Configuracion.h"
#include "Log.h"

ReproductorWav Juego::reproductorWav;


Juego::Juego(VentanaPrincipal *ventanaPrincipal) {
    VistaFichaTablero::CrearImagenes();
    this->puerto_servidor = PUERTO_ESTANDAR_SERVIDOR;
    this->direccion_servidor = DIRECCION_SERVIDOR;

    cargarAudioPredeterminado(true);

    this->ventanaPrincipal = ventanaPrincipal;
    this->partida = NULL;
    nivel = 0;
    idPartidaAsistente = NO_ESTA_UNIDO;
    bufferEnviar = bufferRecibir = estadoCliente = "";
    conectarEventos();
    jsonCompartido = new Json::Value();
    sincronizarTareas = new Mutex();
    finalizoTarea = new bool(false);
    clienteRecibir = new ClientSocketReceiver(sincronizarTareas, this,
            finalizoTarea);
}



void Juego::cargarAudioPredeterminado(bool completo){
    if (completo){
     /* Sonidos no configurables */
     Juego::reproductorWav.addAudio(SON_CLICK, RUTA_SONIDO_CLICK);
     Juego::reproductorWav.addAudio(SON_ERROR, RUTA_SONIDO_ERROR);
     Juego::reproductorWav.addAudio(SON_GANADOR, RUTA_SONIDO_GANADOR);
     Juego::reproductorWav.addAudio(SON_INICIO, RUTA_SONIDO_INICIO);
     Juego::reproductorWav.addAudio(SON_ABANDONO, RUTA_SONIDO_ABANDONO);
     Juego::reproductorWav.addAudio(SON_SIN_MOVIMIENTOS, RUTA_SONIDO_SIN_MOVIMIENTOS);
    }
    /* Sonidos configurables desde editor */
    Juego::reproductorWav.addAudio(SON_COMBINACION, RUTA_SONIDO_COMBINACION_PREDETERMINADO);
    Juego::reproductorWav.addAudio(SON_MOVIMIENTO, RUTA_SONIDO_MOVIMIENTO_PREDETERMINADO);
}

void Juego::mute(){
    Log::loguear(MINFO, "Recuerde cambiar el sonido en todos los clientes!");

	ventanaPrincipal->panelJuego->botonMute.remove();
    if (Juego::reproductorWav.cambiarMute()) {
		ventanaPrincipal->panelJuego->botonMute.add_pixlabel(RUTA_SOUND_ON, "");
	}else {
		ventanaPrincipal->panelJuego->botonMute.add_pixlabel(RUTA_SOUND_OFF, "");
	}
}

void Juego::conectarEventos() {
    ventanaPrincipal->panelLogin->button_loguear.signal_clicked().connect(
        sigc::mem_fun(*this, &Juego::loguearUsuario));

    ventanaPrincipal->panelLogin->button_registrarse.signal_clicked().connect(
        sigc::mem_fun(*this, &Juego::registrarUsuario));

    ventanaPrincipal->panelPartida->button_crear.signal_clicked().connect(
        sigc::mem_fun(*this, &Juego::crearPartida));

    ventanaPrincipal->panelPartida->button_cerrarJuego.signal_clicked().connect(
        sigc::mem_fun(*this, &Juego::terminarJuego));

    ventanaPrincipal->panelJuego->botonVolver.signal_clicked().connect(
        sigc::mem_fun(*this, &Juego::volverSalaPartidas));

    ventanaPrincipal->panelJuego->botonMute.signal_clicked().connect(
        sigc::mem_fun(*this, &Juego::mute));

    ventanaPrincipal->panelJuego->dibujador->signal_something().connect(
        sigc::mem_fun(*this, &Juego::movimientoFicha));

    avisador.connect(sigc::mem_fun(*this, &Juego::avisarCambioInterface));
}

void Juego::terminarJuego() {
    Mensaje mensaje_envio;
    std::string serial;

    mensaje_envio.data["req_type"] = REQ_DESCONECTAR;

    mensaje_envio.setKey(usuario.clave);
    serial = mensaje_envio.serializar();
    clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                       (uint32_t)serial.length());
    clienteEnviar.apagar();
    clienteEnviar.cerrar();
    clienteRecibir->apagar();
    clienteRecibir->cerrar();
    Gtk::Main::quit();
}

void Juego::setPuertoServidor(int puerto) {
    this->puerto_servidor = puerto;
}

void Juego::setDireccionServidor(std::string direccion) {
    this->direccion_servidor = direccion;
}

void Juego::crearPartida() {
    Juego::reproductorWav.reproducir(SON_CLICK);

    if (idPartidaAsistente == NO_ESTA_UNIDO) {
        Mensaje mensaje_envio, mensaje_recibido;
        mensaje_envio.data["req_type"] = REQ_CREAR_PARTIDO;
        mensaje_envio.data["nombre"] = usuario.nick;
        mensaje_envio.data["nivel"] =
                    ventanaPrincipal->panelPartida->obtenerNivel();
        mensaje_envio.data["cantJugadores"] =
            ventanaPrincipal->panelPartida->obtenerCantJugadores();
        mensaje_envio.setKey(usuario.clave);
        std::string serial = mensaje_envio.serializar();
        clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                           (uint32_t)serial.length());
    } else {
        lanzarAviso("Solo puede estar en un partido!");
    }
}

void Juego::agregarPartida(int idPartido, int jugNec, std::string jugConec,
                           int nivel,
                           BotonPartida *bE,
                           BotonPartida *bS,
                           bool meTengoQueUnir) {
    if (jugConec == usuario.nick) {
        idPartidaAsistente = idPartido;
    }

    SalaPartida *partida = new SalaPartida(idPartido, jugNec, jugConec, nivel,
                                           bE,
                                           bS);

    bE->signal_clicked().connect(sigc::mem_fun(*bE,
                                 &BotonPartida::on_button_clicked));

    bE->signal_emit_id().connect(sigc::mem_fun(*this, &Juego::unirAPartida));

    bS->signal_clicked().connect(sigc::mem_fun(*bS,
                                 &BotonPartida::on_button_clicked));

    bS->signal_emit_id().connect(sigc::mem_fun(*this, &Juego::salirPartida));

    ventanaPrincipal->panelPartida->agregarPartida(partida);

    if (idPartidaAsistente != NO_ESTA_UNIDO) {
    this->ventanaPrincipal->panelPartida->bloquearPartidas(idPartidaAsistente);
    }
}

int Juego::obtenerIdPartida() {
    return ventanaPrincipal->panelPartida->obtenerIdPartida();
}

void Juego::salirPartida(int idPartida) {
    if (idPartida != idPartidaAsistente) {
        lanzarAviso("Usted no forma parte de este partido!");
    } else {
        Mensaje mensaje_envio, mensaje_recibido;

        mensaje_envio.data["req_type"] = REQ_OUT_USER;
        mensaje_envio.data["nombre"] = usuario.nick;
        mensaje_envio.data["idPartido"] = idPartida;

        mensaje_envio.setKey(usuario.clave);

        std::string serial = mensaje_envio.serializar();

        borrarUsuario(idPartida, usuario.nick);

        clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                           (uint32_t)serial.length());

        this->ventanaPrincipal->panelPartida->desbloquearPartidas();
    }
}

void Juego::unirmeAPartida(int idPartida) {
    idPartidaAsistente = idPartida;
    this->ventanaPrincipal->panelPartida->bloquearPartidas(idPartida);
}

void Juego::unirAPartida(int idPartida) {
    Juego::reproductorWav.reproducir(SON_CLICK);

    if (idPartidaAsistente == NO_ESTA_UNIDO) {
        Mensaje mensaje_envio, mensaje_recibido;
        mensaje_envio.data["req_type"] = REQ_UNIR_A_PARTIDO;
        mensaje_envio.data["nombre"] = usuario.nick;
        mensaje_envio.data["idPartido"] = idPartida;

        mensaje_envio.setKey(usuario.clave);

        std::string serial = mensaje_envio.serializar();

        clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                           (uint32_t)serial.length());
    } else {
        lanzarAviso("Solo se puede conectar a una partida.");
    }
}

void Juego::borrarPartida(int id) {
    this->ventanaPrincipal->panelPartida->borrarPartida(id);
}

/*controlar el estado de la maquina*/
void Juego::borrarUsuario(int id, std::string nick) {
    if (nick == usuario.nick) {
        idPartidaAsistente = NO_ESTA_UNIDO;
        this->ventanaPrincipal->panelPartida->desbloquearPartidas();
    }

    this->ventanaPrincipal->panelPartida->borrarConectado(id, nick);
}

/*controlar el estado de la maquina*/
void Juego::agregarUsuario(int id, std::string nick) {
    if (nick == "") {
        idPartidaAsistente = id;
        nick = usuario.nick;
        this->ventanaPrincipal->panelPartida->bloquearPartidas(
                                                idPartidaAsistente);
    }

    this->ventanaPrincipal->panelPartida->agregarConectado(id, nick);
}

bool Juego::iniciar() {
    if (conectarAlServidor()){
      start();
    } else {
      lanzarAviso("Error conectando al servidor!");
      return false;
    }
return true;
}

void *Juego::run() {
    mostrarPantallaLogin();
return NULL;
}

bool Juego::conectarAlServidor() {
    clienteEnviar.setDireccion(this->direccion_servidor);
    clienteEnviar.setPuerto(this->puerto_servidor);

    if (clienteEnviar.conectar() == 0) {
        clienteRecibir->setSocket(clienteEnviar.getSocket());
        return true;
    }
return false;
}

void Juego::lanzarAviso(std::string m) {
    this->ventanaPrincipal->lanzarAviso(m);
}

void Juego::mostrarPantallaLogin() {
    ventanaPrincipal->mostrarPantallaLogin();
}

void Juego::registrarUsuario() {
    Juego::reproductorWav.reproducir(SON_CLICK);

    char buffer[4096];
    std::string user = ventanaPrincipal->panelLogin->obtenerUsuario();
    std::string pass = ventanaPrincipal->panelLogin->obtenerPass();

    std::cout << user << std::endl;

    Mensaje mensaje;

    mensaje.setKey(pass);

    mensaje.data["req_type"] = REQ_CREAR_USUARIO;

    mensaje.data["nombre"] = user;

    mensaje.data["password"] = pass;

    std::string serial = mensaje.serializar();

    clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                       (uint32_t)serial.length());

    clienteEnviar.recibirConAvisoDeLong(buffer, 4096);

    Mensaje mensaje_recibido;
    mensaje_recibido.hidratar(buffer);
    mensaje_recibido.imprimir();

    if (mensaje_recibido.getData()["respuesta"] == MENSAJE_USUARIO_CREADO) {
        usuario.nick = user;
        usuario.clave = pass;

        lanzarAviso("Usuario creado con éxito!");
    } else if (mensaje_recibido.getData()["respuesta"]==
                                                    MENSAJE_USUARIO_EXISTE){
        lanzarAviso("El usuario ya existe!");
    }
}

void Juego::loguearUsuario() {
    char buffer[4096];

    Juego::reproductorWav.reproducir(SON_CLICK);
    std::string user = ventanaPrincipal->panelLogin->obtenerUsuario();
    std::string pass = ventanaPrincipal->panelLogin->obtenerPass();

    Mensaje mensaje;
    mensaje.setKey(pass);
    mensaje.data["req_type"] = REQ_AUTENTICAR;
    mensaje.data["nombre"] = user;
    mensaje.data["password"] = pass;
    std::string serial = mensaje.serializar();
    clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                       (uint32_t)serial.length());

    clienteEnviar.recibirConAvisoDeLong(buffer, 4096);

    Mensaje mensaje_recibido;
    mensaje_recibido.hidratar(buffer);

    if (mensaje_recibido.data["respuesta"] == MENSAJE_AUTH_OK) {
        usuario.nick = mensaje_recibido.data["contenido"]["nombre"].asString();
        usuario.clave = pass;
        usuario.nivel = mensaje_recibido.data["contenido"]["nivel"].asInt();
        usuario.uid = mensaje_recibido.data["contenido"]["uid"].asInt();

        ventanaPrincipal->set_title(ENCABEZADO_CLIENTE + usuario.nick);

        solicitarPartidas();

        iniciarHilosEnvioRecepcion();
    } else if (mensaje_recibido.data["respuesta"] == MENSAJE_AUTH_NO_EXISTE) {
        lanzarAviso("Error: el usuario ingresado no existe.");
        Juego::reproductorWav.reproducir(SON_ERROR);
    } else if (mensaje_recibido.data["respuesta"] == MENSAJE_AUTH_NOK) {
        lanzarAviso("Error: usuario o contraseña incorrecta.");
        Juego::reproductorWav.reproducir(SON_ERROR);
    }
}

void Juego::solicitarPartidas() {
    Mensaje mensaje;

    mensaje.setKey(usuario.clave);
    mensaje.data["req_type"] = REQ_PARTIDOS;
    mensaje.data["nombre"] = usuario.nick;

    std::string serial = mensaje.serializar();

    clienteEnviar.enviarConAvisoDeLong((char *)serial.c_str(),
                                       (uint32_t)serial.length());
}

void Juego::cargarPartidas(Json::Value partidos) {
    Json::Value partido;

    for (unsigned int i = 0; i < partidos.size(); i++) {
        partido = partidos[i];

        BotonPartida *botonEntrar =
                        new BotonPartida(partido["idPartido"].asInt());
        BotonPartida *botonSalir =
                        new BotonPartida(partido["idPartido"].asInt());

        agregarPartida(partido["idPartido"].asInt(),
                       partido["jugNecesarios"].asInt(),
                       partido["jugadores"].asString(),
                       partido["nivel"].asInt(),
                       botonEntrar, botonSalir, false);
    }
}

void Juego::movimientoFicha(int x, int y) {
    partida->seleccionarFicha(x, y);
}

void Juego::comenzarPartida() {
    Juego::reproductorWav.reproducir(SON_INICIO);
    ventanaPrincipal->mostrarPantallaJuego();
}

void Juego::mostrarPantallaEleccionPartida(Json::Value niveles) {
    std::map<int, int> *jugadoresPorNivel = new std::map<int, int>();
    for (int i = 0; i < (int)niveles.size(); i++) {
        (*jugadoresPorNivel)[niveles[i]["nivel"].asInt()] =
            niveles[i]["maxJugadores"].asInt();
    }

    ventanaPrincipal->mostrarPantallaEleccionPartida(niveles.size(),
            jugadoresPorNivel);
}

bool Juego::estaEjecutando() {
    return true;
}

void Juego::finalizarJuego() {
}

void Juego::iniciarHilosEnvioRecepcion() {
    clienteRecibir->start();
}

void Juego::notificar(Json::Value mensaje_recibido) {
    *jsonCompartido = mensaje_recibido;

    avisador.emit();
}

void Juego::avisarCambioInterface() {
    Json::Value mensaje = *jsonCompartido;

    int tipoMensaje = mensaje["not_type"].asInt();

    switch (tipoMensaje) {
    case NOT_PARTIDOS:
        Log::loguear(MINFO,"Procesando notificacion not_partidos...");
        if (mensaje["respuesta"].asInt() == 0) {
            cargarPartidas(mensaje["contenido"]["partidos"]);
        }

        mostrarPantallaEleccionPartida(mensaje["contenido"]["niveles"]);
        break;

    case NOT_TYPE_ADD_PARTIDA: {
        Log::loguear(MINFO,"Procesando notificacion add_partida...");
        Json::Value unPartido = mensaje["contenido"]["partido"];

        BotonPartida *botonEntrar =
                        new BotonPartida(unPartido["idPartido"].asInt());
        BotonPartida *botonSalir =
                        new BotonPartida(unPartido["idPartido"].asInt());

        agregarPartida(unPartido["idPartido"].asInt(),
                       unPartido["jugNecesarios"].asInt(),
                       unPartido["jugadores"].asString(),
                       unPartido["nivel"].asInt(),
                       botonEntrar, botonSalir, true);
        break;
    }

    case NOT_OUT_USER:
        Log::loguear(MINFO,"Procesando notificacion out_user...");

        borrarUsuario(mensaje["idPartido"].asInt(),
                      mensaje["nombre"].asString());
        break;

    case NOT_TYPE_ANWSER_JOIN:
        Log::loguear(MINFO,"Procesando notificacion answer_join...");

        if (mensaje["contenido"]["respuesta"].asInt() == 1) {
            agregarUsuario(mensaje["contenido"]["idPartido"].asInt(), "");
        } else {
            lanzarAviso("La sala se ha llenado!");
        }

        break;

    case NOT_TYPE_ADD_USER:
        Log::loguear(MINFO,"Procesando notificacion add_user...");

        agregarUsuario(mensaje["contenido"]["idPartido"].asInt(),
                       mensaje["contenido"]["nombre"].asString());
        break;

    case NOT_TYPE_REMOVE_GAME:
        Log::loguear(MINFO,"Procesando notificacion remove_game...");

        borrarPartida(mensaje["contenido"]["idPartido"].asInt());
        break;

    case NOT_TYPE_START_GAME: {
        Log::loguear(MINFO,"Procesando notificacion start_game...");
        Json::Value partidaJ = mensaje["contenido"];

        int nroCols = partidaJ["nivel"]["numCols"].asInt();
        int nroFilas = partidaJ["nivel"]["numFilas"].asInt();
        std::string pathImag = partidaJ["nivel"]["imagenFondo"].asString();

        partida = new Partida(partidaJ["idPartido"].asInt(),
                              partidaJ["jugadores"], partidaJ["nivel"],
                              partidaJ["tablero"],
                              ventanaPrincipal->panelJuego,
                              nroFilas, nroCols,
                              pathImag,
                              this->ventanaPrincipal->getStatusBar());

        partida->establecerComunicador(&clienteEnviar);
        partida->establecerClave(usuario.clave);

        comenzarPartida();
        break;
    }
    case NOT_UPDATE_BOARD:
        Log::loguear(MINFO,"Procesando notificacion update_board...");
        partida->actualizarPartida(mensaje["movimiento"],
                                   mensaje["jugadores"],
                                   &(mensaje["tableros"]));
        break;

    case NOT_TYPE_INVALID_MOV:
        Log::loguear(MINFO,"Procesando notificacion invalid_mov...");
        partida->animarMovInvalido(mensaje["contenido"]);
        break;

    case NOT_FIN_PARTIDO:
        Log::loguear(MINFO,"Procesando notificacion fin_partido...");
        notificarFinPartida(mensaje["contenido"]);
        break;
    }

    sincronizarTareas->lock();
    sincronizarTareas->avisar();
    sincronizarTareas->unlock();
}

void Juego::notificarFinPartida(Json::Value datosFinPartido) {
    std::string causa = datosFinPartido["causa"].asString();
    std::string mensaje = "";

    if (causa == FIN_GANADOR) {
        partida->actualizarPuntajes(datosFinPartido["ganador"]);
        std::stringstream ss;

        Juego::reproductorWav.reproducir(SON_GANADOR);

        ss << "El ganador es '" << datosFinPartido["ganador"].asString()
           << "' con " << datosFinPartido["puntos"].asInt() << " puntos!";
        mensaje = ss.str();
    } else if (causa == FIN_CANCELACION) {

        Juego::reproductorWav.reproducir(SON_ABANDONO);
        mensaje = "Se han desconectado tus contrincantes!";
    } else {

        Juego::reproductorWav.reproducir(SON_SIN_MOVIMIENTOS);
        mensaje = "No quedan movimientos, no hay ganador...";
    }
    lanzarAviso(mensaje);
    retirarPartida(false);
    solicitarPartidas();
}

void Juego::retirarPartida(bool anunciarRetiro) {
    if (anunciarRetiro)
        partida->notificarRetiro();

    if (partida != NULL) {
        delete partida;
        partida = NULL;
    }

    ventanaPrincipal->panelPartida->borrarPartidas();
    idPartidaAsistente = NO_ESTA_UNIDO;
    this->ventanaPrincipal->panelPartida->desbloquearPartidas();

    Log::loguear(MINFO, "Se reestablecen sonidos predeterminados modificados.");
    cargarAudioPredeterminado(false);
}

void Juego::volverSalaPartidas() {
    retirarPartida(true);

    solicitarPartidas();
}

Juego::~Juego() {
    delete ventanaPrincipal;
    delete finalizoTarea;
    delete jsonCompartido;
    delete sincronizarTareas;

}
